// Copyright (c) 2010 god6or@gmail.com under MIT license.

package ul.gettext;

import java.{io}
import java.{util};
import collection.{mutable};


class GetText(
        var root: String     = "./locale", /// translations root directory
        var domain: String   = "messages", /// translation domain
        var category: String = "MESSAGES", /// translation category
        var lang: String     = "en",       /// current translation language
        var langDef: String  = "en"        /// default (untranslated) translation language
    ) {
    
    val cat = new mutable.HashMap[String, mutable.HashMap[String, Array[Array[String]]]]; // translations catalog in format [lang => [str => [tran1,...tranN]]]
    load; // load/parse catalog
    
    def load = { /// load all translations
        try {
            for (langDir <- new io.File( root ).list()) {
                try {
                    val moFile = new io.FileInputStream(root + io.File.separator + langDir + io.File.separator + 
                        "LC_" + category + io.File.separator + domain + ".mo");
                    val moBytes = (for (i <- 1 to moFile.available()) yield moFile.read().toByte).toArray;
                    //println( "mo: " + moFile + ", len: " + moBytes.length )
                    val moCat = parseMO( moBytes )
                    cat(langDir) = moCat
                } catch { case _ => }
            }
        } catch { case _ => }
    }
    
    def parseMO( mo: Array[Byte] ): mutable.HashMap[String, Array[Array[String]]] = { /// MO file parser
        val lcat = new mutable.HashMap[String, Array[Array[String]]];
        if (mo.length > 24) {
            // check magick for msb/lsb
            val magick = (((mo(0) & 0xff) * 256L + (mo(1) & 0xff)) * 256L + (mo(2) & 0xff)) * 256L + (mo(3) & 0xff);
            val msb = magick == 0x950412deL;
            
            if ((magick == 0x950412deL) || (magick == 0xde120495L)) {
                
                def u32( i: Int ): Long = if (msb)
                    ((((mo(i) & 0xff) * 256L + (mo(i+1) & 0xff)) * 256L + (mo(i+2) & 0xff)) * 256L + (mo(i+3) & 0xff));
                else
                    ((((mo(i+3) & 0xff) * 256L + (mo(i+2) & 0xff)) * 256L + (mo(i+1) & 0xff)) * 256L + (mo(i) & 0xff));
                
                val rev = u32(4); // revision
                val (revMaj, revMin) = (rev >> 16, rev % 65536); // major/minor revision
                val (sn, oto, ott) = (u32(8).toInt, u32(12).toInt, u32(16).toInt); // number of strings, offsets of original and translation strings tables

                
                if (sn > 1 && revMaj <= 1 && revMin <= 1) {
                    for (sc <- 1 to sn-1) { // process all strings
                        // original string(s) length, offset
                        val (osl, oso) = ( u32( oto + 8 * sc ).toInt, u32( oto + 8 * sc + 4 ).toInt );
                        // translation string(s) length, offset
                        val (tsl, tso) = ( u32( ott + 8 * sc ).toInt, u32( ott + 8 * sc + 4 ).toInt );
                        
                        if (osl > 0 && tsl > 0) {
                            // original string(s)
                            val os = mo.slice( oso, oso + osl + 1);
                            // extract all original forms
                            var (oss, ossp) = (List[String](), 0);
                            for (i <- 0 to os.length-1) {
                                if ( os(i) == 0 ) {
                                    oss = oss ::: new String(os.slice(ossp, i), "utf8") :: Nil;
                                    ossp = i + 1;
                                }
                            }
                            // translation string(s)
                            val ts = mo.slice( tso, tso + tsl + 1);
                            // extract all translation forms
                            var (tss, tssp) = (List[String](), 0);
                            for (i <- 0 to ts.length-1) {
                                if ( ts(i) == 0 ) {
                                    tss = tss ::: new String(ts.slice(tssp, i), "utf8") :: Nil;
                                    tssp = i + 1;
                                }
                            }
                            lcat( oss(0) ) = Array( oss.toArray, tss.toArray );
                        }
                    }
                }
                
            }
        }
        return lcat
    }
    
    /// loads translations from .po file
    def parsePo( po:Array[String] ): mutable.HashMap[String, Array[String]] = {
        val lcat = new mutable.HashMap[String, Array[String]];
/*        tranMap.clear // clear map
        localeDir = aLocaleDir
        domainName = aDomainName
        // search for translations
        try {
            for (langDir <- new File(localeDir).list()) {
                try {
                    val poFile = new File(localeDir + File.separator + langDir + File.separator + 
                        messagesDir + File.separator + domainName + ".po")
                    val moFile = new File(localeDir + File.separator + langDir + File.separator + 
                        messagesDir + File.separator + domainName + ".mo")
                    if (poFile.exists()) { // parse .po file
                        tranMap(langDir) = new HashMap[String, String]()
                        val fi = new FileInputStream( poFile )
                        val din = new Array[Byte](poFile.length().toInt)
                        fi.read( din ); fi.close()
                        val poStr = new String(din, poEncoding)
                        val poRe = "(?msu:msgid +\"(.*?)\".*?msgstr +\"(.*?)\")".r
                        for (m <- poRe.findAllIn( poStr ).matchData if ((m.groupCount == 2)&&(m.group(1).length > 0)))
                            tranMap(langDir)(m.group(1)) = m.group(2);
                    } else if (moFile.exists()) { // parse .mo file
                        
                    }
                } catch { case _=> if (logger != null) logger.log("Error loading localization directory " + langDir + ".") }
            }
        } catch { case _=> if (logger != null) logger.log("Error loading localization.") }
*/        
        return lcat;
    }
    
    def tr( s: String, pl: Int, alang: String ): String = { /// translation function
        try {
            return cat(alang)(s)(1)(pl-1);
        } catch {
            case _ => return s;
        }
    }
    def tr( s: String, pl: Int ): String = { /// translation function
        try {
            return cat(lang)(s)(1)(pl-1);
        } catch {
            case _ => return s;
        }
    }
    def tr( s: String ): String = { /// translation function
        try {
            return cat(lang)(s)(1)(0);
        } catch {
            case _ => return s;
        }
    }

    /// returns array of found translation locales
    def langs: List[String] = langDef :: (for ((l,t) <- cat) yield l).toList ::: Nil;

    def displayLangs: List[String] =
        (for (l <- langs) yield {
            val ls = l.split("_");
            val loc = if (ls.length == 1) new util.Locale(l) else new util.Locale(ls(0), ls(1));
            loc.getDisplayName;
        }).toList;
    
}

